package com.bruce.spring.http.client.monitor;

import jdk.internal.net.http.HttpClientFacade;
import jdk.internal.net.http.common.OperationTrackers;
import lombok.Getter;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.net.http.HttpClient;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by bruce in 2025/7/12 00:12
 */
@Component
public class JdkHttpClientMonitor {

    private final ConcurrentHashMap<String, HttpClient> httpClients = new ConcurrentHashMap<>();


    public void registryHttpClient(String name, HttpClient httpClient) {
        httpClients.put(name, httpClient);
    }

    // 移除监控
    public HttpClient removeHttpClient(String name) {
        return httpClients.remove(name);
    }

    public Map<String, Object> getClientTracker(String name) {
        return getTracker(httpClients.get(name));
    }

    public HttpClientConnections connectionsMonitor(String name) {
        HttpClient httpClient = httpClients.get(name);
        if (httpClient == null) {
            return new HttpClientConnections();
        }
        return connectionsMonitor(httpClient);
    }


    public static Map<String, Object> getTracker(HttpClient httpClient) {
        HashMap<String, Object> metrics = new HashMap<>();
        if (httpClient instanceof HttpClientFacade) {
            HttpClientFacade httpClientFacade = (HttpClientFacade) httpClient;
            OperationTrackers.Tracker operationsTracker = httpClientFacade.getOperationsTracker();
            metrics.put("outstandingTcpConnections", operationsTracker.getOutstandingTcpConnections());
            metrics.put("outstandingHttpRequests", operationsTracker.getOutstandingHttpRequests());
            metrics.put("outstandingOperations", operationsTracker.getOutstandingOperations());
            metrics.put("outstandingHttpOperations", operationsTracker.getOutstandingHttpOperations());
            metrics.put("outstandingHttp2Streams", operationsTracker.getOutstandingHttp2Streams());
            metrics.put("outstandingWebSocketOperations", operationsTracker.getOutstandingWebSocketOperations());
            metrics.put("outstandingSubscribers", operationsTracker.getOutstandingSubscribers());
            metrics.put("isFacadeReferenced", operationsTracker.isFacadeReferenced());
            metrics.put("isImplementationReferenced", operationsTracker.isImplementationReferenced());
            metrics.put("isSelectorAlive", operationsTracker.isSelectorAlive());
            metrics.put("name", operationsTracker.getName());
        }
        return metrics;
    }


    public static HttpClientConnections connectionsMonitor(HttpClient httpClient) {
        try {
            return connectionsMonitor0(httpClient);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    @Getter
    public static class HttpClientConnections {
        private final ArrayList<String> http1Connections = new ArrayList<>();
        private final ArrayList<String> http2Connections = new ArrayList<>();

        public void addH1(String h1ClientConnection) {
            http1Connections.add(h1ClientConnection);
        }

        public void addH2(String h2ClientConnection) {
            http2Connections.add(h2ClientConnection);
        }
    }


    private static HttpClientConnections connectionsMonitor0(HttpClient httpClient) throws Exception {
        HttpClientConnections httpClientConnections = new HttpClientConnections();

        if (!(httpClient instanceof HttpClientFacade)) {
            return httpClientConnections;
        }
        // jdk.internal.net.http.HttpClientFacade.impl
        Field field = ReflectionUtils.findField(HttpClientFacade.class, "impl");
        if (field == null) {
            return httpClientConnections;
        }
        field.setAccessible(true);
        Object httpClientImpl = field.get(httpClient);
        Class<?> httpClientImplClass = httpClientImpl.getClass();

        // 解析http1 连接
        // jdk.internal.net.http.HttpClientImpl.connections
        Field http1ConnectionsField = ReflectionUtils.findField(httpClientImplClass, "connections");
        Objects.requireNonNull(http1ConnectionsField);

        http1ConnectionsField.setAccessible(true);
        Object http1ConnectionPool = http1ConnectionsField.get(httpClientImpl);
        // jdk.internal.net.http.ConnectionPool.plainPool
        Field http1PlainConnectionsMapField = ReflectionUtils.findField(http1ConnectionPool.getClass(), "plainPool");
        Objects.requireNonNull(http1PlainConnectionsMapField);
        http1PlainConnectionsMapField.setAccessible(true);
        // HashMap<CacheKey,LinkedList<HttpConnection>>
        HashMap<?, ?> plainPool = new HashMap<>((HashMap<?, ?>) http1PlainConnectionsMapField.get(http1ConnectionPool));
        for (Map.Entry<?, ?> entry : plainPool.entrySet()) {
            LinkedList<?> linkedList = new LinkedList<>((LinkedList<?>) entry.getValue());
            for (Object plainHttpConnection : linkedList) {
                // jdk.internal.net.http.PlainHttpConnection.chan
                String s = reflectAddress(plainHttpConnection);
                httpClientConnections.addH1("http1 " + s);
            }
        }

        // jdk.internal.net.http.ConnectionPool.sslPool
        Field http1SslConnectionsMapField = ReflectionUtils.findField(http1ConnectionPool.getClass(), "sslPool");
        Objects.requireNonNull(http1SslConnectionsMapField);

        http1SslConnectionsMapField.setAccessible(true);
        // HashMap<CacheKey,LinkedList<HttpConnection>>
        HashMap<?, ?> sslPool = new HashMap<>((HashMap<?, ?>) http1SslConnectionsMapField.get(http1ConnectionPool));
        for (Map.Entry<?, ?> entry : sslPool.entrySet()) {
            LinkedList<?> linkedList = new LinkedList<>((LinkedList<?>) entry.getValue());
            for (Object asyncSSLConnection : linkedList) {
                // jdk.internal.net.http.AsyncSSLConnection.plainConnection
                // jdk.internal.net.http.PlainHttpConnection.chan
                Field plainConnectionField = ReflectionUtils.findField(asyncSSLConnection.getClass(), "plainConnection");
                Objects.requireNonNull(plainConnectionField);
                plainConnectionField.setAccessible(true);

                Object plainHttpConnection = plainConnectionField.get(asyncSSLConnection);
                String s = reflectAddress(plainHttpConnection);
                httpClientConnections.addH1("http1 ssl " + s);
            }
        }

        // 解析http2链接
        // jdk.internal.net.http.HttpClientImpl.client2
        Field client2Field = ReflectionUtils.findField(httpClientImplClass, "client2");
        Objects.requireNonNull(client2Field);

        client2Field.setAccessible(true);
        Object http2ClientImpl = client2Field.get(httpClientImpl);
        Class<?> http2ClientImplClass = http2ClientImpl.getClass();
        // jdk.internal.net.http.Http2ClientImpl.connections
        Field http2ConnectionsField = ReflectionUtils.findField(http2ClientImplClass, "connections");
        Objects.requireNonNull(http2ConnectionsField);

        http2ConnectionsField.setAccessible(true);
        /* Map key is "scheme:host:port" */
        // private final Map<String, Http2Connection> connections = new ConcurrentHashMap<>();
        Map<String, ?> connections = new HashMap<>((Map<String, ?>) http2ConnectionsField.get(http2ClientImpl));
        for (Map.Entry<String, ?> stringEntry : connections.entrySet()) {
            Object http2Connection = stringEntry.getValue();
            // jdk.internal.net.http.Http2Connection.connection
            Field connectionField = ReflectionUtils.findField(http2Connection.getClass(), "connection");
            Objects.requireNonNull(connectionField);

            connectionField.setAccessible(true);
            Object httpConnection = connectionField.get(http2Connection);
            // httpConnection 可能是 PlainHttpConnection 或 AsyncSSLConnection
            Object plainHttpConnection = httpConnection;
            Class<?> connectionClass = httpConnection.getClass();
            if (connectionClass.getName().equals("jdk.internal.net.http.AsyncSSLConnection")) {
                Field plainConnectionField = ReflectionUtils.findField(connectionClass, "plainConnection");
                if (plainConnectionField != null) {
                    plainConnectionField.setAccessible(true);
                    plainHttpConnection = plainConnectionField.get(httpConnection);
                }
                String s = reflectAddress(plainHttpConnection);
                httpClientConnections.addH2("http2 ssl " + s);
                continue;
            }
            // // jdk.internal.net.http.PlainHttpConnection.chan
            String s = reflectAddress(plainHttpConnection);
            httpClientConnections.addH2("http2 " + s);
        }
        return httpClientConnections;
    }

    /**
     * @param plainHttpConnection is jdk.internal.net.http.PlainHttpConnection
     * @return socketChannel address
     */
    private static String reflectAddress(Object plainHttpConnection) throws Exception {
        // jdk.internal.net.http.PlainHttpConnection.chan
        Field chanField = ReflectionUtils.findField(plainHttpConnection.getClass(), "chan");
        if (chanField != null) {
            chanField.setAccessible(true);
            SocketChannel socketChannel = (SocketChannel) chanField.get(plainHttpConnection);
            return socketChannel.getLocalAddress() + " --> " + socketChannel.getRemoteAddress();
        }
        throw new Exception("can not find chan field from " + plainHttpConnection.getClass().getName());
    }


}
